package MetricsRetriever.RetrieverComponents;


import java.io.*;
import java.util.*;
import DataStructures.FileRevisionInformation;
import DataStructures.ProjectInformation;
import DataStructures.RevisionInformation;
import General.FileManager;
import General.Globals;
import General.ProcessUtils;
import General.Utilities;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.dav.http.DefaultHTTPConnectionFactory;
import org.tmatesoft.svn.core.internal.io.dav.http.IHTTPConnectionFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.*;
import org.xml.sax.SAXException;

public class SVNManager extends RepositoryManager {

    private String checkoutDir;
    private String url;
    private String repoLink;
    private ProjectInformation pi;
    private SVNClientManager manager;
    private SVNClientManager svncm;
    private List<SVNLogEntry> log = new ArrayList<SVNLogEntry>();
    private SVNRepository repo;
    private SVNDiffClient svndc;
    private SVNUpdateClient svnuc;
    private HashMap<Integer,RevisionInformation> lriMap = new HashMap<Integer, RevisionInformation>() ;
    private List<String> checkouts = new ArrayList<String>();


    public SVNManager(ProjectInformation pi) throws SVNException {
        //this.setupSVN();
        this.checkoutDir = Globals.DATA_DIRECTORY;
        this.url = pi.getUrl();
        this.repoLink = pi.getRepositoryLink();
        this.pi = pi;
        svncm = SVNClientManager.newInstance();
        DAVRepositoryFactory.setup();
        SVNRepositoryFactoryImpl.setup();
        repo = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(url));
        ISVNAuthenticationManager am = SVNWCUtil.createDefaultAuthenticationManager();
        repo.setAuthenticationManager(am);
        svndc = new SVNDiffClient(am, SVNWCUtil.createDefaultOptions(true));
        svnuc = new SVNUpdateClient(am, SVNWCUtil.createDefaultOptions(true));
    }

    public HashMap<Integer,RevisionInformation> getAllRepositoryData(int r) {
        this.helpSVN(r);
        return this.lriMap;
    }

    public void helpSVN(int r){
        try {
            this.retrieveLog();
        } catch (IOException ex) {
            Logger.getLogger(SVNManager.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InterruptedException ex) {
            Logger.getLogger(SVNManager.class.getName()).log(Level.SEVERE, null, ex);
        }catch (ParserConfigurationException ex) {
            Logger.getLogger(SVNManager.class.getName()).log(Level.SEVERE, null, ex);
        }catch (Exception ex) {
            System.out.println("Could not retrieve LOG!");
            return;
        }

            //LOG GIBTS JEZ NICHT MEHR; ALLES IST IN DER LRIMAP direct DRINNEN!!!

            System.out.println("gotlog-"+this.lriMap.size());
            

            // todo: das hier an repositorydata haengen und zurueckgeben an retrievalmanager
            doRevCo(r);
            System.out.println(Thread.currentThread().toString()+"didCheckout"+this.lriMap.size());

            doLineDiffs();
            System.out.println(Thread.currentThread().toString()+"didlinediffs"+this.lriMap.size());
            

            this.countCodeLines(lriMap, new FileManager(pi.getName()));
            System.out.println(Thread.currentThread().toString()+"didanalysis"+this.lriMap.size());
    }

    private void retrieveLog() throws IOException, InterruptedException, ParserConfigurationException, Exception {

        this.setupSVN();
        ArrayList<SVNLogEntry> logEntries = null;
            System.out.println("Retrieving from: "+repoLink);
            SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(repoLink));
            repository.setAuthenticationManager(SVNWCUtil.createDefaultAuthenticationManager());
            long endRevision = -1;
            long startRevision = 0;
            Collection e = repository.log(new String[]{""}, null, startRevision, endRevision, true, true);
            logEntries = new ArrayList<SVNLogEntry>(e);
            for(SVNLogEntry sle : logEntries){
                RevisionInformation ri = new RevisionInformation();
                ri.setComment(sle.getMessage());
                ri.setCommiter(sle.getAuthor());
                ri.setDate(sle.getDate());
                ri.setRevisionNo((int)sle.getRevision());
                //ArrayList<FileRevisionInformation> frilist = new ArrayList<FileRevisionInformation>();
                Collection c = sle.getChangedPaths().values();
                Iterator itr = c.iterator();
                while(itr.hasNext()){
                    SVNLogEntryPath slep = (SVNLogEntryPath) itr.next();
                    if(this.isSourceFile(slep.getPath())){
                        FileRevisionInformation fri = new FileRevisionInformation();
                        fri.setFilename(slep.getPath());
                        if(fri.getFilename().contains("/trunk") && fri.getFilename().length()>7)
                            fri.setFilename(fri.getFilename().substring(fri.getFilename().indexOf("/trunk")+7));
                        fri.setModification(slep.getType());
                        fri.setRevisionNo(ri.getRevisionNo());
                        //WHAT DO WE HAVE STILL?

                        ri.addFileRevisionInformation(fri);
                    }
                }
                lriMap.put(new Integer(ri.getRevisionNo()), ri);

            }
        
        if(logEntries == null || logEntries.size() <1)
            throw new Exception();
        if(pi.getActivity() == 0)
            this.getActivity(logEntries);
    }
    

    private void doRevCo(int rev) {

        /*for(Integer ii : lriMap.keySet()) {

            String path = this.checkoutDir + "/" + pi.getName() + "_" + this.url.hashCode() + "/" + ii.toString();
            boolean success = true;
            int counter = 0;
            while(!success && counter<3){
                counter++;
                this.setupSVN();
                    File destFile = new File(path);
                    if (!destFile.exists() || !destFile.isDirectory()) {
                        destFile.mkdirs();
                    }
                    //check out in a different thread, to timeout after 2h
                    final SVNUpdateClient updateClient = manager.getUpdateClient();
                    final File dFile = destFile;
                    final String svnurl = repoLink;
                    final long rev = ii.longValue();
                    final int c = counter;
                    long finishtime = System.currentTimeMillis() + (2 * 60 * 60 * 1000);
                    /*Thread t = new Thread(new Runnable(){
                        public void run() {
                            *try {
                                updateClient.doExport(SVNURL.parseURIEncoded(svnurl), dFile, SVNRevision.UNDEFINED, SVNRevision.create(rev), "native", true, SVNDepth.INFINITY);
                            } catch (SVNException ex) {
                                System.out.println("PROBLEM at revision "+rev);
                                ex.printStackTrace();
                            }/*
                        }
                    }
                    );
                    t.start();
                    while(t.isAlive() && (System.currentTimeMillis() < finishtime)){
                        try {
                            Thread.sleep(10000l);
                        } catch (InterruptedException ex) {
                            if(counter < 3)
                                success = false;
                        }
                    }
                    if(t.isAlive()){
                        t.stop();
                    }
                    checkouts.add(path);
                try {
                    manager.getWCClient().doCleanup(destFile);
                } catch (SVNException ex) {
                }
               
            }
        }*/
        //for(SVNLogEntry e : log) { // hier jede Revision
        ArrayList<Integer> toDel= new ArrayList<Integer>();
        for(Integer ii : lriMap.keySet()) {
            int i = 3;
            boolean success = false;
            while(i>0 && success == false){
                try {
                    success = true;
                    String co = this.doCheckout(ii.intValue());
                    
                    //File lock = new File(co, ".lock");
                    //lock.createNewFile();
                    if(co == null){
                        toDel.add(ii);
                    }else{
                        lriMap.get(ii).setDirectory(co);
                    }
                } catch(Exception ioe) { // fuer lock.createNewFile. sollte eigentlich gar nicht auftreten.
                    System.out.println("### doRevCo.createNewFile fuer " + pi.getRepositoryLink());
                    ioe.printStackTrace();
                    success = false;
                }
                i--;
            }
        }
        for(Integer iii: toDel){
            System.out.println("removing"+iii);
            lriMap.remove(iii);
        }

    }
        


    // todo: nur im trunk / ueberall
    private void doLineDiffs(){
        if(lriMap.size() < 2) {
            return;
        }
        String cntpath = null;
        String lastpath = null;
        Integer ia[] = new Integer[lriMap.size()];
        int[] keys = this.order(lriMap.keySet().toArray(ia));
        boolean first = true;
        for(int i : keys) {
            String current = lriMap.get(i).getDirectory();
            if(first) {
                lastpath = current;
                first = false;
            } else {
                cntpath = current;
                String cmd = "diff -ru " + lastpath + " " + cntpath;
                System.out.println(cmd);
                String execpath = this.checkoutDir + "/" + pi.getName() + "_" + this.url.hashCode() + "/";
                
                try{
                    //CHECK EXECPATH
                    System.out.println(current);

                    //if(!current.contains("/"))
                      //  return;
                    int rev = i;

                    RevisionInformation ri = lriMap.get(rev);
                    /*int j = 3;
                    boolean success = false;
                    long timeout = 10 * 60 * 1000;
                    ProcessUtils pu = null;
                    while (j>0 && !success){
                            j--;*/
                            long timeout = 10 * 60 * 1000;
                            String outputFile = execpath+rev+"diff";
                            String errorFile = execpath+rev+"diffe";
                            System.out.println("TRYING DIFF ");
                            ProcessUtils pu  = ProcessUtils.callCO(cmd,timeout,execpath+"difffile"+rev);
                        /*if(pu != null)
                            success = true;
                    }
                    if (!success){
                        System.out.println("COULD NOT DIFF REV "+ rev);
                        
                    }
                    else{*/
                    if(pu != null){
                        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(execpath+"difffile"+rev));

                        System.out.println("DOING DIFF FOR REV "+rev+" WHICH IS "+ri);
                        if(ri != null)
                            ri.setLinesChanged(countAddedAndDeletedLines(lriMap.get(rev).getFilerevisions(),bis)); // geht das mit dem i immer?
                        pu.close();
                    }
                    else{pu.close();
                                System.out.println("COULD NOT DIFF!!");
                    }
                    }catch(IOException e){
                        System.out.println("### doLineDiffs fuer " + pi.getRepositoryLink());
                        e.printStackTrace();
                    }
                // jetzt lfri an die db. fri in ci.
                
                lastpath = cntpath;
            }
        }
    }



    private int countAddedAndDeletedLines(ArrayList<FileRevisionInformation> ril, BufferedInputStream diff) throws IOException { // exception propagieren wir hier, damit wir den ganzen prozess in  doRevCo abcatchen koennen.
        if(diff == null) {
            return 0;
        }
        BufferedReader br = new BufferedReader(new InputStreamReader(diff));
        String currFile = null;
        StringBuffer sb = new StringBuffer();
        String line;
        int lineschanged = 0;
        while((line=br.readLine()) != null) {
            System.out.println("DIFF: "+line);
            if(currFile == null && line.startsWith("diff -ru ")) {
                currFile = line.substring(line.lastIndexOf(' '));
            } else if(currFile != null && line.startsWith("diff -ru ")) {
                if(this.isSourceFile(currFile)){
                    lineschanged += countlines(ril, currFile, sb);
                
                }
                sb = new StringBuffer();
                currFile = line.substring(line.lastIndexOf(' '));
            } else {
                sb.append(line);
                sb.append(System.getProperty("line.separator"));
            }
        }

        if(currFile != null) {
            countlines(ril, currFile, sb);
        }
        return lineschanged;

    }

    private int countlines(ArrayList<FileRevisionInformation> lfri, String currFile, StringBuffer sb) {
        int[] lineDiff = new int[3]; // A/D/M

        BufferedReader br = new BufferedReader(new StringReader(sb.toString()));
        String line;
        try {
            while((line=br.readLine()) != null) {
                if(line.startsWith("+") && !line.startsWith("+++")) {
                    lineDiff[0]++;
                } else if(line.startsWith("-") && !line.startsWith("---")) {
                    lineDiff[1]++;
                }
            }
            lineDiff[2] = locRiehle(lineDiff[0], lineDiff[1]);
            int lineschanged = 0;
            for(FileRevisionInformation fri : lfri) {
                //if(currFile.endsWith(path)) {
                System.out.println("file: "+currFile+ "   logfile: "+fri.getFilename());
                //System.out.println("currfile: "+currFile);
                if(currFile.endsWith(fri.getFilename())){
                        System.out.println("Added: "+lineDiff[0]);
                        System.out.println("Deleted: "+lineDiff[1]);
                        System.out.println("Modified: "+lineDiff[2]);
                            fri.setLinesAdded(lineDiff[0]);
                            fri.setLinesDeleted(lineDiff[1]);
                            fri.setLinesModified(lineDiff[2]);

                            return fri.getLinesAdded()+fri.getLinesDeleted();

                    
                }
            }
        } catch(IOException e) {
            System.out.println("### countlines fuer " + pi.getRepositoryLink());
            e.printStackTrace();
        }
        return 0;
    }

    private int locRiehle(int added, int deleted) { // todo: das gehoert in eine superklasse.
        if(0.01269*(double)added + 0.0154*(double)deleted > 2.9965) {
            return (int)(0.9497*(double)added + 0.9744*(double)deleted - 2.9965);
        } else {
            return (int)(0.937*(double)added + 0.959*(double)deleted);
        }
    }

   private String doCheckout(int rev) throws IOException, InterruptedException {
        System.out.println("Started "+rev);
        String path = this.checkoutDir + "/" + pi.getName() + "_" + this.url.hashCode() + "/" + rev;
        File dir = new File(path);
        dir.mkdirs();
        final String cleancmd = "svn cleanup "+path;
        final String command = "svn export -r "+rev +" --force " + repoLink +" "+ path;
        //int i = 3;
        //boolean success = false;
        long timeoutcl = 2 * 60 * 1000;
        long timeoutco = 5 * 60 * 1000;
        //wh//i--;

            String execpath = this.checkoutDir + "/" + pi.getName() + "_" + this.url.hashCode() + "/";
            System.out.println("TRYING CO ");
            String cloutputFile = execpath+rev+"outputcl";
            String clerrorFile = execpath+rev+"errorcl";
            String outputFile = execpath+rev+"output";
            String errorFile = execpath+rev+"error";
            ProcessUtils pu = ProcessUtils.callCO(cleancmd,timeoutcl,path+"cofile");
            if(pu != null)
                pu.close();
            ProcessUtils pu2 = ProcessUtils.callCO(command,timeoutco,path+"cofile");
            if(pu2 == null)
                this.lriMap.remove(new Integer(rev));

            else
                pu2.close();
               
        String pth = new File(path).getAbsolutePath();
        System.out.println("Finished "+rev+ " "+pth);
        return pth;
    }


    private boolean isSourceFile(String file) {
        if(file.endsWith(".java") ||
                file.endsWith(".cpp") || file.endsWith(".cc") || file.endsWith(".c++") || file.endsWith(".cxx") ||
                file.endsWith(".h")|| file.endsWith(".hpp")|| file.endsWith(".hxx")) {
            return true;
        } else {
            return false;
        }
    }

    public String getCheckoutDir() {
        return checkoutDir;
    }

    public void setCheckoutDir(String checkoutDir) {
        this.checkoutDir = checkoutDir;
    }

    public HashMap<Integer, RevisionInformation> getLriMap() {
        return lriMap;
    }

    public List<String> getCheckouts() {
        return checkouts;
    }



     private void setupSVN(){


         manager = SVNClientManager.newInstance();

         IHTTPConnectionFactory fac = new DefaultHTTPConnectionFactory(null,true,null);
         DAVRepositoryFactory.setup(fac);
         //svn://, svn+xxx:// (svn+ssh:// in particular)
         SVNRepositoryFactoryImpl.setup();
         //file:///
         FSRepositoryFactory.setup();
    }

    private int[] order(Integer[] arr) {
        int[] ordered = new int[arr.length];
        int in = 0;
        for(Integer i : arr){
            ordered[in] = i.intValue();
            in++;
        }
        ordered = Utilities.quickSort(ordered, 0, ordered.length-1);
        return ordered;
    }

    private void getActivity(ArrayList<SVNLogEntry> list){
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DATE, -60);
        Date twoMonthsAgo = cal.getTime();
        int recentcommits = 0;
        for(SVNLogEntry entry : list){
            if(entry.getDate().after(twoMonthsAgo))
                recentcommits++;
        }
        if(recentcommits == 0)
            pi.setActivity(0);
        else if(recentcommits < 10)
            pi.setActivity(1);
        else if(recentcommits < 60)
            pi.setActivity(2);
        else
            pi.setActivity(3);
    }


}
